﻿using System;
using System.Collections;
using System.Collections.Generic;
using PT.Common;

namespace PT.Patterns {
    public class Pattern : ICollection<Note> {
        
        readonly List<Note> notes;
        public string Name { get; set; }

        public Rational Duration { get; private set; }

        public Pattern() {
            notes = new List<Note>();
            Duration = new Rational(0, 1);
        }

        public int Count { get { return notes.Count; } }

        /// <summary>Calculates the minimum integer number of <para>partDuration</para> parts in the pattern.
        /// In other words, the method calculates the N from the equation N * parts = M * patterns, so that n and m are both integer.</summary>
        /// <param name="partDuration">Duration of a part.</param>
        /// <returns>Number of parts required to make a region of M patterns</returns>
        public int PatternParts(Rational partDuration) {
            Rational a = partDuration, b = Rational.Reduce(Duration);

            var lcm = MathEx.LeastCommonMultiple(a.Denominator, b.Denominator);
            int aN = a.Numerator * lcm / a.Denominator, bN =  b.Numerator * lcm / b.Denominator;

            return MathEx.LeastCommonMultiple(aN, bN) / aN;
        }

        public Pattern[] Split(Rational partDuration) {
            var partCount = PatternParts(partDuration);
            
            var totalDuration = partDuration * new Rational(partCount, 1);
            var patternRepeats = totalDuration / Duration;

            // Should be a multiple of this Pattern's duration. If not, there's a bug in the calculations previously
            System.Diagnostics.Debug.Assert(patternRepeats.Denominator == 1); 

            var result = new Pattern[partCount];
            for(int i = 0; i < partCount; ++i) {
                result[i] = new Pattern();
            }

            var sum = new Rational(0, 1);

            for (int r = 0; r < patternRepeats.Numerator * Count; ++r) {
                var note = this[r % Count];

                // Calculate the part index to which the note belongs
                var p = (sum / partDuration).Floor();

                result[p].Add(note);
                sum += note.Duration;
            }

            return result;
        }

        public Note this[int index] {
            get { return notes[index]; }
        }

        public override string ToString() {
            return Name;
        }

        #region ICollection<Note> Members
        public IEnumerator<Note> GetEnumerator() {
            return notes.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public void Add(Note item) {
            notes.Add(item);
            Duration = Rational.Add(Duration, item.Duration);
        }

        public void Clear() {
            notes.Clear();
            Duration = new Rational(0, 1);
        }

        public bool Contains(Note item) {
            return notes.Contains(item);
        }

        public void CopyTo(Note[] array, int arrayIndex) {
            notes.CopyTo(array, arrayIndex);
        }

        public bool IsReadOnly {
            get { return false; }
        }

        public bool Remove(Note item) {
            var removed = notes.Remove(item);
            if (removed) {
                Duration = Rational.Subtract(Duration, item.Duration);
            }

            return removed;
        }

        #endregion
    }
}
